package com.ease.actionscript.mapping.mapper;

import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.ClassUtils;
import org.apache.commons.lang.StringUtils;

import xjavadoc.XClass;
import xjavadoc.XMethod;
import xjavadoc.XParameter;

import com.ease.common.util.OutputUtil;

/**
 * 注释 和 参数列表
 * 
 * https://bugs.adobe.com/jira/browse/BLZ-17
 * 
 * @author nathanleewei
 * 
 */
public class ServiceOperationClassMapper extends AbstractActionScriptClassMapper {

	private List<Map<String, Object>> methodObjects = new ArrayList<Map<String, Object>>();

	private Map<String, XMethod> xmethodMap = new HashMap<String, XMethod>();

	private Type[] actualTypeArguments;

	private String remotingDestinationName;

	private String debugClassName = "WorkbenchManagementService";

	public boolean isCompliant() {
		boolean compliant = !clazz.getSimpleName().startsWith("Abstract");// TODO abstract class?
		return compliant && super.isCompliant();
	}

	@SuppressWarnings("unchecked")
	public void prepare() {
		super.prepare();

		Type[] genericInterfaces = clazz.getGenericInterfaces();

		if (null != genericInterfaces && genericInterfaces.length > 0) {
			for (Type genericInterface : genericInterfaces) {
				if (genericInterface instanceof ParameterizedType) {
					actualTypeArguments = ((ParameterizedType) genericInterface).getActualTypeArguments();
					break;
				}
			}
		}

		// this.superclass = clazz.getSuperclass();
		// if (null == superclass) {
		// } else {
		// if (!superclass.equals(Object.class)) {
		// // clazz.isAssignableFrom(Object.class);
		// // o.put("superclass", superclass); //use Proxy instead
		// }
		// }
		this.addImportClassName(CLASS_DEFAULTSERVICEPROXY);

		/** */
		remotingDestinationName = StringUtils.uncapitalize(clazz.getSimpleName());

		if (!actionScriptMappingContext.containRemotingDestinationClass(clazz, this.projectName)) {

			if (applicationContext.containsBean(remotingDestinationName)) {
				Object object = null;
				try {
					object = applicationContext.getBean(remotingDestinationName);
				} catch (Exception e) {
					e.printStackTrace();
				}
				if (null != object) {
					actionScriptMappingContext.addRemotingDestinationClass(clazz, this.projectName);
				}
			}

		}

		/** */
		this.addXMethod(new String[] { projectName }, clazz);
		List<Class<?>> interfaceClasses = (List<Class<?>>) ClassUtils.getAllInterfaces(clazz);
		if (null != interfaceClasses) {
			for (Class<?> interfaceClass : interfaceClasses) {
				boolean result = this.addXMethod(this.getActionScriptMappingContext().getRequireProjectBundle().toArray(new String[0]), interfaceClass);
				if (false == result) {
					this.addXMethod(this.getActionScriptMappingContext().getProjectNames(), interfaceClass);
				}
			}
		}

		/** */
		Method[] methods = clazz.getMethods();// clazz.getDeclaredMethods();

		for (Method method : methods) {

			// method.getDeclaringClass();

			if (Modifier.isPublic(method.getModifiers()) && !isIgnoreMethodName(method)) {
				if (isRemotingMapped(method)) {

					Class<?>[] parameterTypes = method.getParameterTypes();
					// if (null != parameterTypes && parameterTypes.length > 0) {
					// TODO:if(parameterTypes[parameterTypes.length-1])
					// {//last paramaterType==UserContext

					Map<String, Object> methodObject = new HashMap<String, Object>();
					// this.addClassMethod(method);
					methodObjects.add(methodObject);

					methodObject.put("method", method);

					XMethod xmethod = this.getXmethod(method);
					if (null != xmethod) {
						methodObject.put("xmethod", xmethod);
					} else {
						warn("xmethod not found: " + methodKey(method));
					}

					/** */
					Type[] genericParameterTypes = method.getGenericParameterTypes();
					int len = genericParameterTypes.length;
					for (int i = 0; i < len; i++) {
						Type genericParameterType = genericParameterTypes[i];

						Class<?> classType = getParameterClass(genericParameterType, parameterTypes[i]);

						if (null != classType) {
							String typeName = this.getActionScriptTypeName(classType);
							this.addImportClassName(typeName);
						}
					}
					// }
				}
			}
		}

		/** */
		// this.addImportClassName("com.ease.ap.platform.usercontext.vo.UserContext");
		// this.addImportClassName("com.ease.irm.model.system.User");

	}

	/**
	 * List<T> return java.util.List.class
	 * 
	 * @param genericParameterType
	 * @param parameterType
	 * @return
	 */
	protected Class<?> getParameterClass(Type genericParameterType, Class<?> parameterType) {
		// classType = ReflectionUtil.getClass(genericParameterType, 0);
		if (genericParameterType instanceof TypeVariable) {
			Class<?> typeClass = null;
			if (null != actualTypeArguments) {
				typeClass = getParameterActualClass(actualTypeArguments[0]);
			} else {
				typeClass = getParameterActualClass(genericParameterType);
			}
			if (null != typeClass) {
				return typeClass;
			}
		}
		// TODO
		// else { ParameterizedType
		// GenericArrayType
		// Class
		// }
		return parameterType;
	}

	private Class<?> getParameterActualClass(Type type) {
		Class<?> typeClass = null;
		if (type instanceof TypeVariable) {
			TypeVariable<?> typeVariable = (TypeVariable<?>) type;
			Type boundType = typeVariable.getBounds()[0];
			typeClass = (Class<?>) boundType;
		}
		return typeClass;
	}

	public String getGenericActionScriptTypeName(Type genericParameterType, Class<?> parameterType) {
		Class<?> classType = getParameterClass(genericParameterType, parameterType);
		String typeName = null;
		if (null != classType) {
			typeName = this.getActionScriptTypeName(classType);
			StringBuilder sb = new StringBuilder(typeName);
			sb.append("  /* ");
			sb.append(getJavaTypeComment(genericParameterType));
			sb.append(" */");
			return sb.toString();
		}

		typeName = this.getActionScriptTypeName(parameterType);
		if (null != typeName) {
			return typeName;
		}

		return "Object";
	}

	public String getJavaTypeComment(Type type) {
		StringBuilder sb = new StringBuilder();
		getJavaTypeName(type, sb);
		return sb.toString();
	}

	public void getJavaTypeName(Type type, StringBuilder sb) {
		if (type instanceof ParameterizedType) {
			ParameterizedType parameterizedType = (ParameterizedType) type;

			Class<?> parameterizedTypeClass = (Class<?>) parameterizedType.getRawType();
			sb.append(parameterizedTypeClass.getName()).append("<");

			Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
			for (int i = 0; i < actualTypeArguments.length; i++) {
				getJavaTypeName(actualTypeArguments[i], sb);
				if (i < actualTypeArguments.length - 1) {
					sb.append(",");
				}
			}
			// Class<?> typeClass = ReflectionUtil.getClass(parameterizedType, 0);
			// sb.append(typeClass.getName());
			sb.append(">");
		} else if (type instanceof GenericArrayType) {
			GenericArrayType genericArrayType = (GenericArrayType) type;
			Class<?> typeClass = (Class<?>) genericArrayType.getGenericComponentType();
			sb.append(typeClass.getName());
		} else if (type instanceof Class) {
			Class<?> typeClass = (Class<?>) type;
			if (typeClass.isArray()) {
				String canonicalName = typeClass.getCanonicalName();
				String elementTypeName = canonicalName.substring(0, canonicalName.length() - "[]".length());
				sb.append(elementTypeName);
			} else {
				sb.append(typeClass.getName());
			}
		}
	}

	@SuppressWarnings("unchecked")
	public boolean addXMethod(String[] projectNames, Class<?> clazz) {

		XClass xclass = getXclass(projectNames, clazz);
		if (null != xclass) {
			//
			// Class<?> superclass = clazz.getSuperclass();
			List<XMethod> xmethods = (List<XMethod>) xclass.getMethods();

			if (null != xmethods) {
				for (XMethod xmethod : xmethods) {
					String key = xmethodKey(xclass, xmethod);
					// xmethod.getDoc().toString();
					// List<XParameter> xparameters=xmethod.getParameters();
					// xparameters.get(1).getName();
					xmethodMap.put(key, xmethod);
				}
			}
			return true;
		}
		return false;
	}

	@SuppressWarnings("unchecked")
	public String xmethodKey(XClass xclass, XMethod xmethod) {
		StringBuilder sb = new StringBuilder();
		sb.append(xclass.getName());
		sb.append("+");
		sb.append(xmethod.getName());
		sb.append("(");

		// sb.append(xmethod.getNameWithSignature(false));
		List<XParameter> xparameters = (List<XParameter>) xmethod.getParameters();
		if (null != xparameters) {
			int len = xparameters.size();
			for (int i = 0; i < len; i++) {
				XParameter xparameter = xparameters.get(i);
				XClass parameterType = xparameter.getType();
				// sb.append(parameterType.getName());//如果示泛型将无法识别
				String parameterPackageName = parameterType.getQualifiedName();
				// if (parameterPackageName.indexOf('.') > -1) {
				// parameterPackageName = parameterPackageName.substring(0,
				// parameterPackageName.lastIndexOf('.'));
				// }
				sb.append(parameterPackageName);
				if (i < len - 1) {
					sb.append(",");
				}
			}
		}

		sb.append(")");

		String key = sb.toString();
		if (debugClassName.equals(clazz.getSimpleName())) {
			OutputUtil.p("xmethod: " + key);
		}
		return key;
	}

	public String methodKey(Method method) {
		Class<?> declaringClass = method.getDeclaringClass();

		StringBuilder sb = new StringBuilder();
		sb.append(declaringClass.getSimpleName());
		sb.append("+");
		sb.append(method.getName());
		sb.append("(");

		Type[] genericParameterTypes = method.getGenericParameterTypes();

		if (null != genericParameterTypes) {
			int len = genericParameterTypes.length;
			for (int i = 0; i < len; i++) {
				Type genericParameterType = genericParameterTypes[i];
				if (genericParameterType instanceof TypeVariable) {
					sb.append(declaringClass.getPackage().getName() + "." + ((TypeVariable<?>) genericParameterType).getName());
				} else if (genericParameterType instanceof ParameterizedType) {
					ParameterizedType parameterizedType = (ParameterizedType) genericParameterType;
					Class<?> typeClass = (Class<?>) parameterizedType.getRawType();
					sb.append(typeClass.getName());
				} else if (genericParameterType instanceof GenericArrayType) {
					GenericArrayType genericArrayType = (GenericArrayType) genericParameterType;
					Class<?> typeClass = (Class<?>) genericArrayType.getGenericComponentType();
					sb.append(typeClass.getName());
				} else if (genericParameterType instanceof Class) {
					Class<?> typeClass = (Class<?>) genericParameterType;
					if (typeClass.isArray()) {
						String canonicalName = typeClass.getCanonicalName();
						sb.append(canonicalName.substring(0, canonicalName.length() - "[]".length()));
					} else {
						sb.append(typeClass.getName());
					}
				} else {
					sb.append(genericParameterType.toString());
				}
				// sb.append(parameterType.getName());
				// String parameterPackageName = parameterType.getName();
				// if (parameterPackageName.indexOf('.') > -1) {
				// parameterPackageName = parameterPackageName.substring(0,
				// parameterPackageName.lastIndexOf('.'));
				// }
				// sb.append(genericParameterType.get);
				if (i < len - 1) {
					sb.append(",");
				}
			}
		}

		// Class<?>[] parameterTypes = method.getParameterTypes();
		// if (null != parameterTypes) {
		// int len = parameterTypes.length;
		// for (int i = 0; i < len; i++) {
		// Class<?> parameterType = parameterTypes[i];
		// parameterType.getGenericSuperclass();
		//
		// // sb.append(parameterType.getName());
		// String parameterPackageName = parameterType.getName();
		// if (parameterPackageName.indexOf('.') > -1) {
		// parameterPackageName = parameterPackageName.substring(0,
		// parameterPackageName.lastIndexOf('.'));
		// }
		// sb.append(parameterPackageName);
		// if (i < len - 1) {
		// sb.append(",");
		// }
		// }
		// }

		sb.append(")");

		String key = sb.toString();
		if (debugClassName.equals(clazz.getSimpleName())) {
			OutputUtil.p("method: " + key);
		}
		return key;
	}

	public XMethod getXmethod(Method method) {
		if (null != xmethodMap) {
			return xmethodMap.get(methodKey(method));
		}
		return null;
	}

	/**
	 * @return the remotingDestinationName
	 */
	public String getRemotingDestinationName() {
		return remotingDestinationName;
	}

	/**
	 * @param remotingDestinationName
	 *            the remotingDestinationName to set
	 */
	public void setRemotingDestinationName(String remotingDestinationName) {
		this.remotingDestinationName = remotingDestinationName;
	}

	/**
	 * @return the methodObjects
	 */
	public List<Map<String, Object>> getMethodObjects() {
		return methodObjects;
	}

	/**
	 * @param methodObjects
	 *            the methodObjects to set
	 */
	public void setMethodObjects(List<Map<String, Object>> methodObjects) {
		this.methodObjects = methodObjects;
	}

}
